import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;


public class Payload implements Serializable
{
        private static final long serialVersionUID = 1L;
    String request;
    String operation;
    String reply;
    String packetType;
    int view;
    int replicaNum;
    int senderPid;
    int clientPort;
    long timestamp;
    int maxSeqNum;
    byte[] payloadSignature;
    PublicKey publicKey;
        Payload clientPayload;
        String digest;
        int sequenceNum;
       int lastStableCheckpoint;
    //init
    ArrayList<ArrayList<String>> listPrepareProof;
    ArrayList<String> listCheckpointProof;
    ArrayList<String> listViewChangeAccepted;
    ArrayList<Payload > remainingPrePrepares;
    public Payload()
    {
        payloadSignature = new byte[Constants.SIG_SIZE];
        Arrays.fill(payloadSignature, 0,Constants.SIG_SIZE,(byte)0);
        packetType = Constants.EMPTY_STR;
        operation = Constants.EMPTY_STR;
        request = Constants.EMPTY_STR;
        reply = Constants.EMPTY_STR;
        view = Constants.MINUS_ONE;
        replicaNum = Constants.MINUS_ONE;
        senderPid = Constants.MINUS_ONE;
        timestamp = Constants.MINUS_ONE;
        sequenceNum=Constants.ZERO;
        clientPayload=null;
        digest=Constants.EMPTY_STR;
        clientPort = 0;
        lastStableCheckpoint =0;
        maxSeqNum =0;
        listPrepareProof = new ArrayList<ArrayList<String>>();
        listCheckpointProof = new ArrayList<String>();
        listViewChangeAccepted = new ArrayList<String>();
        remainingPrePrepares = new ArrayList<Payload>();
    }


    public byte[] payloadByteArray()
    {
            String allElements = request+view+replicaNum+senderPid;
            return allElements.getBytes();
    }
}      

class PayloadBuilder
{  
    public byte[] buildRequest(String op,long t,int pid, PeerKeyPair keyPair,int port)
    {
        Payload payload = new Payload();
        payload.request = op;
        payload.senderPid = pid;
        payload.timestamp = t;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.REQUEST;
        payload.clientPort = port;
        return serializePayload(payload);
    }

    public byte[] buildPrePrepare(int view, int seqNum,int pid, PeerKeyPair keyPair,String digest,Payload clientPayload)
    {
        Payload payload = new Payload();
        payload.view = view;
	    payload.senderPid=pid;
	    payload.sequenceNum= seqNum;
	    payload.digest=digest;
	    payload.clientPayload = clientPayload;
	    payload.publicKey = keyPair.pubKey;
	    payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.PREPREPARE;
        return serializePayload(payload);
    }

    public byte[] buildPrepare(int view, int seqNum,int pid, PeerKeyPair keyPair,String digest,Payload clientPayload)
    {
        Payload payload = new Payload();
        payload.view = view;
        payload.senderPid=pid;
        payload.sequenceNum= seqNum;
        payload.digest=digest;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.PREPARE;
        payload.clientPayload = clientPayload;
        return serializePayload(payload);
    }

    public byte[] buildCommit(int view, int seqNum,int pid, PeerKeyPair keyPair,String digest,Payload clientPayload)
    {
        Payload payload = new Payload();
        payload.view = view;
        payload.senderPid=pid;
        payload.sequenceNum= seqNum;
        payload.digest=digest;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.COMMIT;
        payload.clientPayload = clientPayload;
        return serializePayload(payload);
    }

    public byte[] buildReply(int view, int seqNum,int pid, PeerKeyPair keyPair,String digest,Payload clientPayload,String reply)
    {
        Payload payload = new Payload();
        payload.view = view;
        payload.senderPid=pid;
        payload.sequenceNum= seqNum;
        payload.digest=digest;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.REPLY;
        payload.clientPayload = clientPayload;
        payload.reply = reply;
        return serializePayload(payload);
    }

    public byte[] buildCheckpoint(int view,int seqNum,int pid, PeerKeyPair keyPair,String digest)
    {
        Payload payload = new Payload();
        payload.view = view;
        payload.senderPid=pid;
        payload.sequenceNum= seqNum;
        payload.digest=digest;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.CHECKPOINT;
        return serializePayload(payload);
    }

    
    public byte[] buildViewChange(int view,int pid, PeerKeyPair keyPair, int lastStableCheckpoint,
			ArrayList<String> listProofCheckpoint, ArrayList<ArrayList<String>> listProofPrepare,int maxSeqNum)
    {
        Payload payload = new Payload();
        payload.lastStableCheckpoint = lastStableCheckpoint;
        payload.listCheckpointProof = listProofCheckpoint;
        payload.listPrepareProof  = listProofPrepare;
        payload.view = view;
        payload.senderPid = pid;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.VIEWCHANGE;
        payload.maxSeqNum = maxSeqNum;
        return serializePayload(payload);
    }

    public byte[] buildNewView(int view,int pid, PeerKeyPair keyPair, ArrayList<String> listViewChangeAccepted,
    		ArrayList<Payload> remainingPrePrepares,int lastStableCheckpoint,int maxSeqNum)
    {
        Payload payload = new Payload();
        payload.lastStableCheckpoint = lastStableCheckpoint;
        payload.listViewChangeAccepted = listViewChangeAccepted;
        payload.remainingPrePrepares  = remainingPrePrepares;
        payload.view = view;
        payload.senderPid = pid;
        payload.publicKey = keyPair.pubKey;
        payload.payloadSignature = Utilities.signMyData(payload.payloadByteArray(), keyPair.privKey);
        payload.packetType = Constants.NEWVIEW;
        payload.maxSeqNum = maxSeqNum;
        return serializePayload(payload);
    }

   

    public Payload deserializePayload(DatagramPacket packet)
    {
        Payload payload = null;
        byte[] dataBuffer = new byte[Constants.PAYLOAD_SIZE];
        try
        {
                Arrays.fill(dataBuffer, 0,Constants.PAYLOAD_SIZE,(byte)0);    
                dataBuffer = packet.getData();
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(dataBuffer));
                payload = (Payload) ois.readObject();
                ois.close();
        }
        catch (ClassNotFoundException cnf)
        {
                System.out.println("ERROR in deserialization - "+cnf.getMessage());
        }
        catch (IOException io)
        {
                // TODO Auto-generated catch block
                System.out.println("ERROR in deserialization - "+io.getMessage());
        }
       
        return payload;
    }
   
    public static byte[] serializePayload(Payload payload)
    {
        byte[] packetData = new byte[Constants.PAYLOAD_SIZE];
        try
        {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos   = new ObjectOutputStream(bos);
                oos.writeObject(payload);
                oos.flush();
                oos.close();
                bos.close();
                packetData = bos.toByteArray();
                
        }
        catch (IOException io)
        {
                // TODO Auto-generated catch block
                System.out.println("ERROR in serialization - "+io.getMessage());
        }
        return packetData;
    }

}

